/*
 * $Id: JXHyperlink.java 4162 2012-02-08 16:21:35Z kschaefe $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package org.jdesktop.swingx;

import java.awt.Color;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URI;

import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.plaf.ButtonUI;

import org.jdesktop.beans.JavaBean;
import org.jdesktop.swingx.hyperlink.AbstractHyperlinkAction;
import org.jdesktop.swingx.hyperlink.HyperlinkAction;
import org.jdesktop.swingx.plaf.HyperlinkAddon;
import org.jdesktop.swingx.plaf.LookAndFeelAddons;

/**
 * A hyperlink component that derives from JButton to provide compatibility
 * mostly for binding actions enabled/disabled behavior accessibility i18n
 * etc...
 * <p>
 *
 * This button has visual state related to a notion of "clicked": foreground
 * color is unclickedColor or clickedColor depending on its boolean bound
 * property clicked being false or true, respectively. If the hyperlink has an
 * action, it guarantees to synchronize its "clicked" state to an action value
 * with key LinkAction.VISITED_KEY. Synchronization happens on setAction() and
 * on propertyChange notification from the action. JXHyperlink accepts any type
 * of action - {@link AbstractHyperlinkAction} is a convenience implementation
 * to simplify clicked control.
 * <p>
 * 
 * <pre>
 * <code>
 *      LinkAction linkAction = new LinkAction("http://swinglabs.org") {
 *            public void actionPerformed(ActionEvent e) {
 *                doSomething(getTarget());
 *                setVisited(true);
 *            }
 *      };
 *      JXHyperlink hyperlink = new JXHyperlink(linkAction);
 * <code>
 * </pre>
 * 
 * The hyperlink can be configured to always update its clicked property after
 * firing the actionPerformed:
 * 
 * <pre>
 * <code>
 *      JXHyperlink hyperlink = new JXHyperlink(action);
 *      hyperlink.setOverrulesActionOnClick(true);
 * <code>
 * </pre>
 * 
 * By default, this property is false. The hyperlink will auto-click only if it
 * has no action. Developers can change the behaviour by overriding
 * {@link JXHyperlink#isAutoSetClicked()};
 * 
 * @author Richard Bair
 * @author Shai Almog
 * @author Jeanette Winzenburg
 */
@JavaBean
public class JXHyperlink extends JButton {

	/**
	 * @see #getUIClassID
	 * @see #readObject
	 */
	public static final String uiClassID = "HyperlinkUI";

	// ensure at least the default ui is registered
	static {
		LookAndFeelAddons.contribute(new HyperlinkAddon());
	}

	private boolean hasBeenVisited = false;

	/**
	 * Color for the hyper link if it has not yet been clicked. This color can
	 * be set both in code, and through the UIManager with the property
	 * "JXHyperlink.unclickedColor".
	 */
	private Color unclickedColor;

	/**
	 * Color for the hyper link if it has already been clicked. This color can
	 * be set both in code, and through the UIManager with the property
	 * "JXHyperlink.clickedColor".
	 */
	private Color clickedColor;

	private boolean overrulesActionOnClick;

	/**
	 * Creates a new instance of JXHyperlink with default parameters
	 */
	public JXHyperlink() {
		this(null);
	}

	/**
	 * Creates a new instance of JHyperLink and configures it from provided
	 * Action.
	 *
	 * @param action
	 *            Action whose parameters will be borrowed to configure newly
	 *            created JXHyperLink
	 */
	public JXHyperlink(Action action) {
		super();
		setAction(action);
		init();
	}

	/**
	 * Convenience method to create and install a HyperlinkAction for the given
	 * URI.
	 * 
	 * @param uri
	 *            to uri to create a HyperlinkAction for, maybe null.
	 * @throws HeadlessException
	 *             if {@link GraphicsEnvironment#isHeadless()} returns
	 *             {@code true}
	 * @throws UnsupportedOperationException
	 *             if the current platform doesn't support Desktop
	 * 
	 * @see HyperlinkAction#createHyperlinkAction(URI)
	 */
	public void setURI(URI uri) {
		setAction(HyperlinkAction.createHyperlinkAction(uri));
	}

	/**
	 * Returns the foreground color for unvisited links.
	 * 
	 * @return Color for the hyper link if it has not yet been clicked.
	 */
	public Color getUnclickedColor() {
		return unclickedColor;
	}

	/**
	 * Sets the color for the previously visited link. This value will override
	 * the one set by the "JXHyperlink.clickedColor" UIManager property and
	 * defaults.
	 *
	 * @param color
	 *            Color for the hyper link if it has already been clicked.
	 */
	public void setClickedColor(Color color) {
		Color old = getClickedColor();
		clickedColor = color;
		if (isClicked()) {
			setForeground(getClickedColor());
		}
		firePropertyChange("clickedColor", old, getClickedColor());
	}

	/**
	 * Returns the foreground color for visited links.
	 * 
	 * @return Color for the hyper link if it has already been clicked.
	 */
	public Color getClickedColor() {
		return clickedColor;
	}

	/**
	 * Sets the color for the previously not visited link. This value will
	 * override the one set by the "JXHyperlink.unclickedColor" UIManager
	 * property and defaults.
	 *
	 * @param color
	 *            Color for the hyper link if it has not yet been clicked.
	 */
	public void setUnclickedColor(Color color) {
		Color old = getUnclickedColor();
		unclickedColor = color;
		if (!isClicked()) {
			setForeground(getUnclickedColor());
		}
		firePropertyChange("unclickedColor", old, getUnclickedColor());
	}

	/**
	 * Sets the clicked property and updates visual state depending on clicked.
	 * This implementation updated the foreground color.
	 * <p>
	 * 
	 * NOTE: as with all button's visual properties, this will not update the
	 * backing action's "visited" state.
	 * 
	 * @param clicked
	 *            flag to indicate if the button should be regarded as having
	 *            been clicked or not.
	 * @see #isClicked()
	 */
	public void setClicked(boolean clicked) {
		boolean old = isClicked();
		hasBeenVisited = clicked;
		setForeground(isClicked() ? getClickedColor() : getUnclickedColor());
		firePropertyChange("clicked", old, isClicked());
	}

	/**
	 * Returns a boolean indicating if this link has already been visited.
	 * 
	 * @return <code>true</code> if hyper link has already been clicked.
	 * @see #setClicked(boolean)
	 */
	public boolean isClicked() {
		return hasBeenVisited;
	}

	/**
	 * Sets the overrulesActionOnClick property. It controls whether this button
	 * should overrule the Action's visited property on actionPerformed.
	 * <p>
	 * 
	 * The default value is <code>false</code>.
	 * 
	 * @param overrule
	 *            if true, fireActionPerformed will set clicked to true
	 *            independent of action.
	 * 
	 * @see #getOverrulesActionOnClick()
	 * @see #setClicked(boolean)
	 */
	public void setOverrulesActionOnClick(boolean overrule) {
		boolean old = getOverrulesActionOnClick();
		this.overrulesActionOnClick = overrule;
		firePropertyChange("overrulesActionOnClick", old, getOverrulesActionOnClick());
	}

	/**
	 * Returns a boolean indicating whether the clicked property should be set
	 * always on clicked.
	 * 
	 * @return overrulesActionOnClick false if his button clicked property
	 *         respects the Action's visited property. True if the clicked
	 *         should be updated on every actionPerformed.
	 * 
	 * @see #setOverrulesActionOnClick(boolean)
	 * @see #setClicked(boolean)
	 */
	public boolean getOverrulesActionOnClick() {
		return overrulesActionOnClick;
	}

	/**
	 * {@inheritDoc}
	 * <p>
	 * Overridden to respect the overrulesActionOnClick property.
	 */
	@Override
	protected void fireActionPerformed(ActionEvent event) {
		super.fireActionPerformed(event);
		if (isAutoSetClicked()) {
			setClicked(true);
		}
	}

	/**
	 * Returns a boolean indicating whether the clicked property should be set
	 * after firing action events. Here: true if no action or overrulesAction
	 * property is true.
	 * 
	 * @return true if fireActionEvent should force a clicked, false if not.
	 */
	protected boolean isAutoSetClicked() {
		return getAction() == null || getOverrulesActionOnClick();
	}

	/**
	 * Creates and returns a listener that will watch the changes of the
	 * provided <code>Action</code> and will update JXHyperlink's properties
	 * accordingly.
	 */
	@Override
	protected PropertyChangeListener createActionPropertyChangeListener(final Action a) {
		final PropertyChangeListener superListener = super.createActionPropertyChangeListener(a);
		// JW: need to do something better - only weak refs allowed!
		// no way to hook into super
		return new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if (AbstractHyperlinkAction.VISITED_KEY.equals(evt.getPropertyName())) {
					configureClickedPropertyFromAction(a);
				} else {
					superListener.propertyChange(evt);
				}

			}

		};
	}

	/**
	 * Read all the essential properties from the provided <code>Action</code>
	 * and apply it to the <code>JXHyperlink</code>
	 */
	@Override
	protected void configurePropertiesFromAction(Action a) {
		super.configurePropertiesFromAction(a);
		configureClickedPropertyFromAction(a);
	}

	private void configureClickedPropertyFromAction(Action a) {
		boolean clicked = false;
		if (a != null) {
			clicked = Boolean.TRUE.equals(a.getValue(AbstractHyperlinkAction.VISITED_KEY));

		}
		setClicked(clicked);
	}

	private void init() {
		setForeground(isClicked() ? getClickedColor() : getUnclickedColor());
	}

	/**
	 * Returns a string that specifies the name of the L&F class that renders
	 * this component.
	 */
	@Override
	public String getUIClassID() {
		return uiClassID;
	}

	/**
	 * Notification from the <code>UIManager</code> that the L&F has changed.
	 * Replaces the current UI object with the latest version from the
	 * <code>UIManager</code>.
	 * 
	 * @see javax.swing.JComponent#updateUI
	 */
	@Override
	public void updateUI() {
		setUI((ButtonUI) LookAndFeelAddons.getUI(this, ButtonUI.class));
	}
}
